# -*- coding: utf8 -*-
import threading, random, time, sys


# estos parametros pueden modificarse para crear diferentes circunstancias
N = 5              # numero de filosofos
RONDAS = 5        # numero de ingesas de cada comensal para saciarse
T_PENSANDO = 1   # maximo tiempo pensando
T_COMIENDO = 1     # maximo tiempo comiendo
PAUSA = 0.10       # tiempo de reaccion tras tomar el tenedor izquierdo
T_SESION = 20 		#duracion maxima de la cena
MODO = 0           # define si se usa el modo mejora




#El modo mejora aplica una solución al problema central de los filosofos
#Esta simple mejora evita interbloqueo (deadlock), pero el proceso necesita
#más tiempo, por lo tanto la mayoría de los procemos morirán de inanición

# definiciones para la salida
PIENSA = ".P."
LLENO = ".L."
ESPERA = ".E."
IZQ = "|    "
DER = "    |"
SINUSO = "  |  "

CORRIENDO = True
ULTIMO=False

def wait(limite=2):
    """Espera un tiempo aleatorio."""
    time.sleep(random.random() * limite)

estado = ({}, {})           # 1. diccionario: filosofos, 2. diccionarios: tenedores
estado_lock = threading.Lock()

start_lock = threading.Lock()


class Filosofo(threading.Thread):
    def __init__(self, id):
        threading.Thread.__init__(self)
        self.id = id
        self.tenedor_izquierdo = tenedores[(id - 1) % N]
        self.tenedor_derecho = tenedores[id]
        self.ingestas = 0
        self.setestado(PIENSA)
        # los filosofos son demonios
        self.setDaemon(True)  

    def setestado(self,status):
        estado_lock.acquire()
        estado[0][self.id] = status
        estado_lock.release()

    def run(self):
        while (self.ingestas < RONDAS) and CORRIENDO:              
            self.pensar()
            self.conseguir_tenedor()
            self.comer()
            self.soltar_tenedores()
        if self.ingestas == RONDAS:
            self.setestado(LLENO)
            printestado("  - %d esta lleno!" % self.id)
            
    def pensar(self):
        self.setestado(PIENSA)
        printestado("  - %d piensa." % self.id)
        wait(T_PENSANDO)

    def comer(self):
        self.ingestas = self.ingestas + 1
        self.setestado("%2d " % self.ingestas) 
        printestado("  - %d come." % self.id)
        wait(T_COMIENDO)

    def conseguir_tenedor(self):
        if MODO == 1:
            #si el filosofo no puede agarrar el tenedor derecho
            #deja el izquierdo, y recomienza a intentarlo
            while True:
                self.tenedor_izquierdo.acquire(self.id)
                self.setestado(ESPERA)
                printestado("  - %d espera." % self.id)
                time.sleep(PAUSA)                       #espera activa
                if self.tenedor_derecho.duenio == None:
                    self.tenedor_derecho.acquire(self.id)
                    break
                else:
                    self.tenedor_izquierdo.release()
                    time.sleep(PAUSA)
        else:
            self.tenedor_izquierdo.acquire(self.id)
            self.setestado(ESPERA)
            printestado("  - %d espera." % self.id)
            time.sleep(PAUSA)
            self.tenedor_derecho.acquire(self.id)
                
        

    def soltar_tenedores(self):
        self.tenedor_izquierdo.release()
        self.tenedor_derecho.release()


class Tenedor(object):
    def __init__(self, i):
        self.lock = threading.Lock()
        self.id = i
        self.duenio = None
        self.setestado(SINUSO)

    def setestado(self,status):
        estado_lock.acquire()
        estado[1][self.id] = status
        estado_lock.release()

    def acquire(self, phid, blocking=True):
        if self.lock.acquire(blocking):
            self.duenio = phid
            if self.id == phid:
                self.setestado(IZQ)
            else:
                self.setestado(DER)
            printestado()
            return True
        printestado()
        return False

    def release(self, remark=""):
        self.lock.release()
        self.duenio = None
        self.setestado(SINUSO)
        printestado(remark)

# ----------  SALIDA  -------------

def printestado(suff=""):
    if not CORRIENDO: return
    global ultimo
    ts = transcurrido() 
    s = " "
    estado_lock.acquire()
    for i in range(N):
        s += estado[0][i]
        s += estado[1][i]
    if s.endswith("|"):
        s = "|" + s[1:-1] + " "
    if ultimo != s:
        ultimo = s
        print ts + s + suff
    estado_lock.release()

# tiempo transcurrido
def transcurrido():
    now = time.clock()
    return "%6.2f -- " % (now - t_inicio)


#se fija la situacion al final si hubo interbloqueo, inanición
# o todos terminaron llenos y felices
def finalizar():
    global CORRIENDO, ULTIMO
    ULTIMO=True
    CORRIENDO = False
    time.sleep(0.05) # esperar si falta terminar de imprimir
                     
    print transcurrido(), "Resultado final:\n"
    todos_esperan = True
    for f in estado[0]:
        if estado[0][f] != ESPERA:
            todos_esperan = False
    if todos_esperan:
        print "\n-------------------------"
        print transcurrido(), "-- INTERBLOQUEO!" 
        print "-------------------------\n"

    todos_llenos = True
    for f in estado[0]:
        if estado[0][f] != LLENO:
            print "%3d muerto de inanición!" % f
            todos_llenos = False
    if todos_llenos:
        print "Todos los filosofos están llenos!"
        
    print "\n******************** FIN ********************"


# ----------   hilo principal   -------------


start_lock.acquire()

    
# crea tenedores:
ultimo = ""
tenedores = []
for i in range(N):
    tenedores.append(Tenedor(i))

# creo filosofos
filosofos = []
for i in range(N):
    f = Filosofo(i)
    filosofos.append(f)

t_inicio = time.clock()
# ... y les doy vida
for f in filosofos:
    f.start()


# soltando estaba el filosofo
t = threading.Timer(T_SESION, finalizar)
t.start()

